/*
 ============================================================================
 Name        : Scheduler.h
 Author      : James McQueen, Corwyn Simpson, Craig Markham
 Version     :
 Date        : 05/14/2012
 Description : TCSS-422 Operating Systems Scheduler
 ============================================================================
 */

#include <stdlib.h>
#include "Scheduler.h"
#include "IODevice.h"

#define MAX_RING_BUFFER 16
#define OPEN_MUTEX 0


//shared memory global variable
int global_var = 0;
int read_semaphore = 0;
int write_semaphore = 1;

// deallocates any terminated processeseseseses (I was cold)
// and returns the next non-terminated Process in the queue
PCB* getNonTerminated(RingBuffer* queue)
{
    PCB* result = (PCB*) ringbuff_get(queue);
    //Remove any terminated processes first
    while (pcb_getState(result) == TERM)
    {
        process_destroy(pcb_getProcess(result));
        pcb_destroy(result);
        result = (PCB*) ringbuff_get(queue);
    }
    // result is now a PCB* that isn't in the TERM state
    return result;
}


Scheduler* scheduler_init(){
	int i = 0;
    Scheduler* sched = (Scheduler*) malloc(sizeof(Scheduler));
    //Allocate the ready queue
    sched->my_ready_queue = ringbuff_init(MAX_RING_BUFFER);
    //allocate the io-blocked queues
    sched->my_io_blocked_queues = (RingBuffer**) malloc(NUM_IODEVICES * sizeof(RingBuffer*));

    for (i = 0; i < NUM_IODEVICES; i++)
    {
        sched->my_io_blocked_queues[i] = ringbuff_init(MAX_RING_BUFFER);
    }
    //allocate the mutex-blocked-queues
    sched->my_mutex_blocked_queues = (RingBuffer**) malloc(NUM_MUTEX * sizeof(RingBuffer*));
    for (i = 0; i < NUM_MUTEX; i++)
    {
        sched->my_mutex_blocked_queues[i] = ringbuff_init(MAX_RING_BUFFER);
    }
    
    return sched;
}

//Moves processes around based on type of interrupt
void scheduler_interrupt(Scheduler* sched, int type)
{
    PCB* process;
    char msg[200];

    switch(type){

    case IODEVICE_TIMER:
        //switch running Process state to READY and place in back of Ready Queue
        sched->my_running->my_state = READY;
        ringbuff_put(sched->my_ready_queue, (void*)sched->my_running);
        sprintf(msg,"Process %d placed in Ready Queue due to Timer Interrupt\n",
        		pcb_getPID(sched->my_running));
        logger_msg(msg);
        
        //Get the next READY process
        sched->my_running = getNonTerminated(sched->my_ready_queue);
        //now set the legitimate process to the running state
        pcb_setState(sched->my_running, RUN);
        sprintf(msg,"Process %d Running\n", pcb_getPID(sched->my_running));
        logger_msg(msg);
        break;

    case IODEVICE_KBD:
        if (sched->my_io_blocked_queues[IODEVICE_KBD]->size > 0)
        { //if there is a head
            //move the head of the kbd blocked queue to end of ready queue
            process = getNonTerminated(sched->my_io_blocked_queues[IODEVICE_KBD]);
            //ensure getNonTerminated did not return a null value
            if(process != NULL){
				pcb_setState(process, READY);
				ringbuff_put(sched->my_ready_queue, process);
		        sprintf(msg,"Process %d placed in Ready Queue due to KBD Req complete.\n",
		        		pcb_getPID(process));
		        logger_msg(msg);
            }
        }
        break;

    case IODEVICE_VID:
        if (sched->my_io_blocked_queues[IODEVICE_VID]->size > 0)
        { //if there is a head
            //move the head of the vid blocked queue to end of ready queue
            process = getNonTerminated(sched->my_io_blocked_queues[IODEVICE_VID]);
            //ensure getNonTerminated did not return a null value
            if(process != NULL){
            	pcb_setState(process, READY);
                ringbuff_put(sched->my_ready_queue, process);
		        sprintf(msg,"Process %d placed in Ready Queue due to Video Req complete.\n",
		        		pcb_getPID(process));
		        logger_msg(msg);
            }
        }
        break;

    case IODEVICE_DISK:
        if (sched->my_io_blocked_queues[IODEVICE_DISK]->size > 0)
        { //if there is a head
            //move the head of the kbd blocked queue to end of ready queue
            process = getNonTerminated(sched->my_io_blocked_queues[IODEVICE_DISK]);
            //ensure getNonTerminated did not return a null value
            if(process != NULL){
				pcb_setState(process, READY);
				ringbuff_put(sched->my_ready_queue, process);
		        sprintf(msg,"Process %d placed in Ready Queue due to Disk Req complete.\n",
		        		pcb_getPID(process));
		        logger_msg(msg);
            }
        }
        break;
    }
}

void scheduler_io_request(Scheduler* sched, int num)
{
	char msg[200];
    //Move the running Process to the blocked IO queue for device num
    pcb_setState(sched->my_running, BLOCKED);
    ringbuff_put(sched->my_io_blocked_queues[num], sched->my_running);
	sprintf(msg,"Process %d placed in io blocked queue num %d.\n", pcb_getPID(sched->my_running), num);
	logger_msg(msg);
    //get the next process from the ready queue, and run it
    sched->my_running = getNonTerminated(sched->my_ready_queue);
    pcb_setState(sched->my_running, RUN);
	sprintf(msg,"Process %d Running.\n", pcb_getPID(sched->my_running));
	logger_msg(msg);
}

void scheduler_mutex_acq(Scheduler* sched, int num)
{
	char msg[200];
	  if(sched->my_mutex_locks[num] == 0)
    {
		//set mutex lock to process id
		sched->my_mutex_locks[num] = sched->my_running->PID;
		sprintf(msg,"Process %d acquired Mutex lock num %d.\n", pcb_getPID(sched->my_running), num);
		logger_msg(msg);

	  } else{
		  //ensure lock is not already owned by requesting process
		  if(sched->my_mutex_locks[num] != sched->my_running->PID){
			//switch running Process state to BLOCKED and place in back of appropriate mutex Queue
			sched->my_running->my_state = BLOCKED;
			ringbuff_put(sched->my_mutex_blocked_queues[num], (void*)sched->my_running);

			sprintf(msg,"Process %d placed in blocked queue for Mutex lock num %d.\n", pcb_getPID(sched->my_running), num);
			logger_msg(msg);

			//Front of Ready Queue Process to Running
			sched->my_running = (PCB*) ringbuff_get(sched->my_ready_queue);
			sched->my_running->my_state = RUN;
			sprintf(msg,"Process %d Running.\n", pcb_getPID(sched->my_running));
			logger_msg(msg);
		  }
	 }
}

void scheduler_mutex_give(Scheduler* sched, int num){
	PCB * process;
	char msg[200];

	 if(sched->my_mutex_locks[num] == sched->my_running->PID){
		//set mutex lock to 0
		sched->my_mutex_locks[num] = 0;
	    sprintf(msg,"Process %d released Mutex lock num %d.\n", pcb_getPID(sched->my_running), num);
	    logger_msg(msg);
     // unblock the first Process in the right mutex_blocked_queue
        if (sched->my_mutex_blocked_queues[num]->size > 0)
        { //if there is a head
            //move the head of the mutex_blocked_queue to end of ready queue
            process = getNonTerminated(sched->my_mutex_blocked_queues[num]);
            //ensure getNonTerminated did not return a null value
            if(process != NULL){
				pcb_setState(sched->my_running, READY);
				ringbuff_put(sched->my_ready_queue, process);
			    sprintf(msg,"Process %d acquired Mutex lock num %d.\n", pcb_getPID(process), num);
			    logger_msg(msg);
            }

            //process unblocked now acquires the mutex lock
  		  //set mutex lock to process id
  		  sched->my_mutex_locks[num] = pcb_getPID(process);
        }

	 }
}

void scheduler_mem_write(Scheduler* sched, int num){
	char msg[200];
	if(write_semaphore == 1){
		global_var = pcb_getPID(sched->my_running);
		read_semaphore = 1;
		write_semaphore = 0;

		sprintf(msg,"Process %d Writes Shared Memory. Read flag set.\n", pcb_getPID(sched->my_running));
		logger_msg(msg);
	}
}

void scheduler_mem_read(Scheduler* sched, int num){
	char msg[200];
	if(read_semaphore == 1){
		read_semaphore = 0;
		write_semaphore = 1;
		sprintf(msg,"Process %d Read Shared Memory. Write flag set.\n", pcb_getPID(sched->my_running));
		logger_msg(msg);
	}
}
